//Class File to describe each construction block
using System;
using System.Collections;
using System.Drawing;

namespace SiRtEt
{
	public enum MoveTo
	{
		Left,Right,Down,Rotate
	}
	
	/// <summary>
	/// The BlockBase corresponds to each construction block in the game world. It will be made up
	/// of various number of Unit Squares, and will also give the current position, rotation for
	/// the block.
	/// Various blocks will thus use this as the base class to describe their individual 
	/// characteristics.
	/// It also renders the block in the specified render area. 
	/// </summary>
	public class BlockBase
	{
		#region Members
			public int X,Y;						// The co-ordinates
			public int Rows,Columns;			// Space occupied
			public int Height, Width;			// Space again... in different units
			public int RotateAngle;				// How much is current rotation
			public TheWorld BelongsTo;			// The World to which the block belongs to
			public Color	MyColor;			// The color of each block for rendering
			
		#endregion

		#region Constructor
		/// <summary>
		/// Constructor : Initializes all the members
		/// </summary>
		/// <param name="theWorld"> The Game World to which the block belongs to</param>
		public BlockBase(TheWorld theWorld)
		{
			BelongsTo	= theWorld;					// Setting connection with the world
			Rows		= theWorld.Rows;			// Rows in the world
			Columns		= theWorld.Columns;			// Columns in the world

			RotateAngle = 0;						// Starts straight

		}
		#endregion

		#region Methods

			#region Virtual Methods : UsedSquares, Preview
				/// <summary>
				/// A virtual function that will return the indices of the squares that
				/// this block occupies. 
				/// </summary>
				/// <returns></returns>
				public virtual ArrayList UsedSquares()
				{
					return null;
				}

				/// <summary>
				/// Function to draw a preview of the figure in specified rendering area
				/// </summary>
				/// <param name="renderArea">The rendering place</param>
				public virtual void Preview(Graphics renderArea){}
				
			#endregion

			#region Rendering : RenderBlock, ClearBlock
				/// <summary>
				/// Function to render the block in the render area. It actually just marks the required
				/// squares as filled and thus does not itself need a reference to the render area ( Was
				/// tryin it like that b4)
				/// </summary>
				public void RenderBlock()
				{
					// For all the squares occupied by this particular block, set it to be filled
					// so that next rendering of the world fills it up with reqd color and add the
					// squares to the list of Colored squares in the world
					foreach(int usedSquare in UsedSquares())
					{
						// Setting the square to be updated in next render cycle
						BelongsTo.SquaresToInvalidate.Add(BelongsTo.TheSquares[usedSquare]);
						
						// Setting the square to be filled in and specifying the fill color
						BelongsTo.TheSquares[usedSquare].ColorFlag = true;
						BelongsTo.TheSquares[usedSquare].FillColor = MyColor;
					}
				}

				/// <summary>
				/// Function to clear the block from the render area. Similar to the rendering, it just
				/// invalidates the squares occupied by the block and marks the square as 'not filled'
				/// Not much stuff to do in the render area as such other than removing the square from
				/// the colored list
				/// </summary>
				public void ClearBlock()
				{
					// For all the occupied squares in the block, invalidate and make the Fill flag to
					// false
					foreach(int usedSquare in UsedSquares())
					{
						// Setting the square to be repainted in next render cycle
						BelongsTo.SquaresToInvalidate.Add(BelongsTo.TheSquares[usedSquare]);
						// Setting the fill flag to false
						BelongsTo.TheSquares[usedSquare].ColorFlag = false;
					}
				}
			#endregion

			#region Movement : IsPossibleToDraw, Move, IndexSetDifference, MovePossible
			/// <summary>
			/// Function to check if the current figure is possible to draw
			/// </summary>
			/// <returns>True or False</returns>
			public bool IsPossibleToDraw()
			{
				// Get all the squares that will be filled by this figure and if any of them are
				// already filled, then this figure aint possible
				foreach(int usedSquare in UsedSquares())
					if(BelongsTo.TheSquares[usedSquare].ColorFlag == true)
						return false;				

				// None used so can be possibly drawn....
				return true;				
			}


			/// <summary>
			/// Subtraction of the 2 sets of indices passed as ArrayLists
			/// </summary>
			/// <param name="IndexSet1"></param>
			/// <param name="IndexSet2"></param>
			/// <returns>IndexSet - IndexSet1</returns>
			private ArrayList IndexSetDifference(ArrayList IndexSet1, ArrayList IndexSet2)
			{
				ArrayList theDifference = new ArrayList();
				foreach(int index in IndexSet2)
				{
					if(IndexSet1.Contains(index))
						continue;
					theDifference.Add(index);
				}

				return theDifference;
			}
		

			/// <summary>
			/// Check if the block can be moved in the given direction
			/// </summary>
			/// <returns></returns>
			public bool MovePossible(MoveTo moveDirection)
			{
				ArrayList currentIndices = UsedSquares();
				ArrayList futureIndices = new ArrayList();

				switch(moveDirection)
				{
					case MoveTo.Down:
						if(Y + Height == Rows) // reached end 
							return false;

						// Faking a move and forming a list of the squares occupied in that situation
						Y+=1;
						futureIndices = UsedSquares();
						Y-=1;
						break;
					case MoveTo.Left:
						if(X==0)	// extreme left
							return false;
						// Faking a move to left and making list of squares occupied in that situation
						X-=1;
						futureIndices = UsedSquares();
						X+=1;
						break;
					case MoveTo.Right:
						if(X + Width == Columns)	// extreme right
							return false;
						// Faking a move to right and making list of squares occupied in that situation
						X+=1;
						futureIndices = UsedSquares();
						X-=1;
						break;
					case MoveTo.Rotate:

						int currentRotation = RotateAngle;
						// Faking the rotation
						RotateBlock();
						futureIndices = UsedSquares();
						RotateAngle=currentRotation;
						if(X+Width > Columns)			// Extreme right
							return false;
						if(Y+Height > Rows)				// Bottom
							return false;

						break;
				}

				// Calculating the difference in the current and the future indices
				ArrayList difference = IndexSetDifference(currentIndices,futureIndices);

				// If any of the squares overlap, we cant move
				foreach(int square in difference)
					if(BelongsTo.TheSquares[square].ColorFlag)
						return false;

				// Now we can
				return true;
			}


		private void RotateBlock()
		{
			RotateAngle +=90;
			if(RotateAngle>270)
				RotateAngle = 0;
		}

			/// <summary>
			/// Move the block in the specified direction after checking if it can actually be moved
			/// </summary>
			/// <param name="moveDirection">The direction of movement requested</param>
			public void Move(MoveTo moveDirection)
			{
				#region Check if move possible
				if(!MovePossible(moveDirection))
				{
					// As this figure cant go any lower, we need a new block to enter the world
					if(moveDirection==MoveTo.Down)
						BelongsTo.NewBlockReqd = true;
					return;
				}
				#endregion

				// To move the block : 
				// 1. Clear it 
				// 2. Move it in reqd direction 
				// 3. Render it
				// 4. Refresh 
				
				//1.
				ClearBlock();

				//2.
				switch(moveDirection)
				{
					case MoveTo.Left:
						X-=1;
						break;
					case MoveTo.Right:
						X+=1;
						break;
					case MoveTo.Down:
						Y+=1;
						break;
					case MoveTo.Rotate:
						RotateBlock();
						break;
				}

				//3.
				RenderBlock();

				//4. Refresh the invalid squares in the world
				BelongsTo.RefreshSquares();				
			}
			
			#endregion

		#endregion

	}
}
